/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */

package sk.jp.jitag;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import sk.jp.commons.exception.ApplicationException;
import sk.jp.commons.tools.Properties;
import sk.jp.commons.utils.ExceptionUtils;
import sk.jp.commons.utils.LogMsg;
import sk.jp.jitag.dao.JitagDao;
import sk.jp.jitag.model.config.Config;
import sk.jp.jitag.util.Defaults;
import sk.jp.jitag.util.Property;

/**
 * Manages application configuration.
 */
@Component
public class ConfigManager {

    private static final Logger log = LoggerFactory.getLogger(ConfigManager.class);

    public static final String APP_HOME_DIR = ".jitag";
    public static final String APP_CONFIG_DIR = "conf/";
    public static final String CONFIG_FILE = "jitag.db";
    public static final String PROPERTY_FILE = "jitag.properties";
    public static final String CONFIG_SCHEMA_FILE = "config-1.0.xsd";

    //----------------------------------------------------------------------

    @Autowired
    private JitagDao jitagDao;

    private File configFile = null;
    private Config config = null;

    //----------------------------------------------------------------------

    /**
     * Loads configuration from file to internal state.
     */
    public synchronized void loadConfig(String confPlace, String confPath){
        if(log.isDebugEnabled()){
            log.debug(LogMsg.inp("confPlace", confPlace, "confPath", confPath));
        }

        File file = resolveConfFilePath(confPlace, confPath);
        configFile = file;
        if(!file.exists() || !file.canRead()){
            log.warn(LogMsg.toString("No config file found on specified place or can not be read, use default",
                                     "confPlace", confPlace, "confPath", confPath,
                                     "filePath", file.getAbsoluteFile()));
            config = new Config();
        }else{
            jitagDao.beginTransaction();
            try {
                jitagDao.beginTransaction();
                config = jitagDao.getConfig();
                if(config == null){
                    config = new Config();
                }
                jitagDao.commitTransaction();
            } catch (Exception e) {
                jitagDao.rollbackTransaction(e);
                config = new Config();
                log.warn("Can't read or parse configuration file, use default", e);
            } finally {
                jitagDao.close();
            }
        }

        if(log.isTraceEnabled()){
            log.trace(LogMsg.toString("Configuration loaded", "config", config));
        }
    }

    /**
     * Returns currently loaded configuration.
     * @return currently loaded configuration.
     */
    public synchronized Config getConfig() {
        checkIfLoaded();
        return config;
    }

    /**
     * Returns directory of currently loaded configuration.
     * @return directory of currently loaded configuration.
     */
    public synchronized File getConfigDir() {
        if(configFile == null){
            throw new IllegalStateException("Configuration is not loaded");
        }
        return configFile.getParentFile();
    }

    /**
     * Saves current configuration to file in current configuration directory.
     */
    public synchronized void saveConfig(){
        saveConfig(configFile);
    }

    /**
     * Saves current configuration to file in current configuration directory.
     * @param newConfigFile
     */
    public synchronized void saveConfig(File newConfigFile){
        checkIfLoaded();
        if(log.isTraceEnabled()){
            log.trace(LogMsg.inp("newConfigFile", newConfigFile, "currentConfig", config));
        }else if(log.isDebugEnabled()){
            log.debug(LogMsg.inp("newConfigFile", newConfigFile));
        }

        jitagDao.beginTransaction();
        try {
            jitagDao.saveConfig(config);
            jitagDao.commitTransaction();
            if(!configFile.equals(newConfigFile)){
                jitagDao.copyDatabaseFile(newConfigFile.getAbsolutePath());
                this.configFile = newConfigFile;
                log.info(LogMsg.toString("Switched to new config file location", 
                                         "newConfigFile", newConfigFile.getAbsolutePath()));
            }
            jitagDao.commitTransaction();
        }catch(ApplicationException ae){
            jitagDao.rollbackTransaction(ae);
            throw ae;
        }catch(Throwable e){
            jitagDao.rollbackTransaction(e);
            throw new ApplicationException("Can't save configuration to database",
                                           null,    //TODO Dokoncit!!! code
                                           null, e);
        }
    }

    /**
     * Save base application configuration properties to file.
     */
    public synchronized void saveBaseProperties(){
        log.debug(LogMsg.inp());
        String propertFile = APP_CONFIG_DIR + PROPERTY_FILE;
        try {
            Map<String, String> propertiesToSave = propertiesToMap(Property.values());
            Properties.saveProperties(propertiesToSave, new File(propertFile));
        } catch (Exception e) {
            throw new ApplicationException("Can't save base configuration properties",
                                           null,
                                           ExceptionUtils.paramsToMap("propertFile",
                                                                      propertFile),
                                           e);
        }
    }

    /**
     * Resolves configuration file path.
     * @param confPlace place (see {@link Defaults.ConfigPlace}).
     * @param confPath custom path used if configPlace is 'path'.
     * @return File instance that points to path.
     */
    public File resolveConfFilePath(String confPlace, String confPath) {
        log.trace(LogMsg.inp("confPlace", confPlace, "confPath", confPath));
        String filePath = null;
        Defaults.ConfigPlace configPlace = Defaults.ConfigPlace.valueOf(confPlace);
        if(configPlace == Defaults.ConfigPlace.app){
            filePath = APP_CONFIG_DIR + CONFIG_FILE;
        }else if(configPlace == Defaults.ConfigPlace.user){
            filePath = System.getProperty("user.home") + "/" + APP_HOME_DIR + "/" + CONFIG_FILE;
        }else if(configPlace == Defaults.ConfigPlace.path){
            filePath = confPath;
            if(!filePath.endsWith("/") && !filePath.endsWith("\\")){
                filePath = filePath + "/";
            }
            filePath = filePath + CONFIG_FILE;
        }
        log.trace(LogMsg.out("path", filePath));
        return new File(filePath);
    }

    //----------------------------------------------------------------------

    private synchronized void checkIfLoaded() {
        if(config == null){
            throw new IllegalStateException("Configuration is not loaded");
        }
    }

    private Map<String,String> propertiesToMap(Property... properties){
        Map<String, String> propertiesToSave = new LinkedHashMap<String, String>();
        for (Property property : properties) {
            propertiesToSave.put(property.getPropKey(), property.value());
        }
        return propertiesToSave;
    }

}
